﻿using System.Reflection;
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Autofac.Core.Resolving.Pipeline;
using Microsoft.Extensions.DependencyInjection;

namespace WeDonekRpc.NetCoreModular;

/// <summary>
/// Extension methods for registering ASP.NET Core dependencies with Autofac.
/// </summary>
internal static class AutofacRegistration
{
    public static void Populate (
        this ContainerBuilder builder,
        IEnumerable<ServiceDescriptor> descriptors )
    {
        foreach ( ServiceDescriptor descriptor in descriptors )
        {
            Type? implementationType = descriptor.NormalizedImplementationType();
            if ( implementationType != null )
            {
                TypeInfo serviceTypeInfo = descriptor.ServiceType.GetTypeInfo();
                if ( serviceTypeInfo.IsGenericTypeDefinition )
                {
                    _ = builder
                        .RegisterGeneric(implementationType)
                        .ConfigureServiceType(descriptor)
                        .ConfigureLifecycle(descriptor.Lifetime, null);
                }
                else
                {
                    _ = builder
                        .RegisterType(implementationType)
                        .ConfigureServiceType(descriptor)
                        .ConfigureLifecycle(descriptor.Lifetime, null);
                }

                continue;
            }

            if ( descriptor.IsKeyedService && descriptor.KeyedImplementationFactory != null )
            {
                IComponentRegistration registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, ( context, parameters ) =>
                {
                    ResolveRequestContext requestContext = (ResolveRequestContext)context;

                    IServiceProvider serviceProvider = context.Resolve<IServiceProvider>();

                    Autofac.Core.KeyedService keyedService = (Autofac.Core.KeyedService)requestContext.Service;

                    object key = keyedService.ServiceKey;

                    return descriptor.KeyedImplementationFactory(serviceProvider, key);
                })
                .ConfigureServiceType(descriptor)
                .ConfigureLifecycle(descriptor.Lifetime, null)
                .CreateRegistration();

                builder.RegisterComponent(registration);

                continue;
            }
            else if ( !descriptor.IsKeyedService && descriptor.ImplementationFactory != null )
            {
                IComponentRegistration registration = RegistrationBuilder.ForDelegate(descriptor.ServiceType, ( context, parameters ) =>
                {
                    IServiceProvider serviceProvider = context.Resolve<IServiceProvider>();
                    return descriptor.ImplementationFactory(serviceProvider);
                })
                    .ConfigureServiceType(descriptor)
                    .ConfigureLifecycle(descriptor.Lifetime, null)
                    .CreateRegistration();

                builder.RegisterComponent(registration);
                continue;
            }
            _ = builder
                .RegisterInstance(descriptor.NormalizedImplementationInstance()!)
                .ConfigureServiceType(descriptor)
                .ConfigureLifecycle(descriptor.Lifetime, null)
                .ExternallyOwned();
        }
    }

    private static IRegistrationBuilder<object, TActivatorData, TRegistrationStyle> ConfigureServiceType<TActivatorData, TRegistrationStyle> (
        this IRegistrationBuilder<object, TActivatorData, TRegistrationStyle> registrationBuilder,
        ServiceDescriptor descriptor )
    {
        if ( descriptor.IsKeyedService )
        {
            object key = descriptor.ServiceKey!;

            // If it's keyed, the service key won't be null. A null key results in it _not_ being a keyed service.
            _ = registrationBuilder.Keyed(key, descriptor.ServiceType);
        }
        else
        {
            _ = registrationBuilder.As(descriptor.ServiceType);
        }

        return registrationBuilder;
    }

    private static IRegistrationBuilder<object, TActivatorData, TRegistrationStyle> ConfigureLifecycle<TActivatorData, TRegistrationStyle> (
        this IRegistrationBuilder<object, TActivatorData, TRegistrationStyle> registrationBuilder,
        ServiceLifetime lifecycleKind,
        object? lifetimeScopeTagForSingleton )
    {
        switch ( lifecycleKind )
        {
            case ServiceLifetime.Singleton:
                if ( lifetimeScopeTagForSingleton == null )
                {
                    _ = registrationBuilder.SingleInstance();
                }
                else
                {
                    _ = registrationBuilder.InstancePerMatchingLifetimeScope(lifetimeScopeTagForSingleton);
                }

                break;
            case ServiceLifetime.Scoped:
                _ = registrationBuilder.InstancePerLifetimeScope();
                break;
            case ServiceLifetime.Transient:
                _ = registrationBuilder.InstancePerDependency();
                break;
        }

        return registrationBuilder;
    }
    public static object? NormalizedImplementationInstance ( this ServiceDescriptor descriptor )
    {
        return descriptor.IsKeyedService ? descriptor.KeyedImplementationInstance : descriptor.ImplementationInstance;
    }


    public static Type? NormalizedImplementationType ( this ServiceDescriptor descriptor )
    {
        return descriptor.IsKeyedService ? descriptor.KeyedImplementationType : descriptor.ImplementationType;
    }
}
